/*
 * NativeAudioDevice.cpp
 *
 *  Created on: 2018年8月27日
 *      Author: zhengboyuan
 */

#include "NativeAudioDevice.h"
#include "CLog.h"
#include "MediaType.h"
#include "StopWatch.h"
#include "ClockTime.h"


#define SDL_MIX_MAXVOLUME   128

static const size_t FULL_QUEUE_SIZE = 8;
static const int    MAX_BUFFER_COUNT = 4;


namespace av
{

void NativeAudioDevice::StartUp()
{

}

void NativeAudioDevice::Cleanup()
{

}


void NativeAudioDevice::slBufferQueueCallback(SLAndroidSimpleBufferQueueItf caller, void *context)
{
    NativeAudioDevice* pRender = (NativeAudioDevice*)context;
    pRender->audioCallback(caller);
}


NativeAudioDevice::NativeAudioDevice():
    m_engineObject(),
    m_engineEngine(),
    m_outputMixObject(),
    m_playerObject(),
    m_playerPlay(),
    m_playerBufferQueue(),
    m_playerVolume(),
    m_playerConfig(),
    m_pcmFormat(),
    m_volume(SDL_MIX_MAXVOLUME),
    m_isMute(),
    m_state(av::STATE_STOPPED),
    m_enabled(true),
    m_timePoint(),
    m_frameCount(),
    m_endOfStream()
{
    CLog::debug("NativeAudioDevice::NativeAudioDevice\n");
}

NativeAudioDevice::~NativeAudioDevice()
{
    close();
}

bool NativeAudioDevice::open(int channels, int rate, int bits)
{
    if ((rate <= 0) || (channels < 1))
    {
        return false;
    }

    close();

    openEngine();

    bool done = openDevice(rate, channels);

    return done;
}

void NativeAudioDevice::close()
{
    if (!isOpen())
    {
        return;
    }

    if (m_state == av::STATE_PLAYING)
    {
        stop();
    }
    else
    {
        m_dataEvent.post();
    }

    closeDevice();

    cleanup();

    closeEngine();
}

bool NativeAudioDevice::isOpen()
{
    return isDeviceOpen();
}

void NativeAudioDevice::write(uint8_t* data, int size, int64_t pts)
{
    //CLog::info("NativeAudioDevice::write. data: %p, size: %d, pts: %lld\n", data, size, pts);

    MediaPacketPtr pkt(new MediaPacket());
    pkt->pts = pts;
    pkt->write(data, size);

    writeFrame(pkt);
}

float NativeAudioDevice::getVolume()
{
    return (double)m_volume / SDL_MIX_MAXVOLUME;
}


bool NativeAudioDevice::setVolume(float vol)
{
    if (vol < 0)
    {
        vol = 0;
    }
    else if (vol > 1.0)
    {
        vol = 1.0;
    }

    int volume = (int)(vol * SDL_MIX_MAXVOLUME);

    comn::AutoCritSec lock(m_cs);
    m_volume = volume;

    return true;
}


bool NativeAudioDevice::isMute()
{
    return m_isMute;
}


bool NativeAudioDevice::setMute(bool muted)
{
    comn::AutoCritSec lock(m_cs);
    m_isMute = muted;

    return true;
}

void NativeAudioDevice::pause(bool toPause)
{
    CLog::info("NativeAudioDevice::pause %d, isOpen: %d\n", toPause, isOpen());

    if (!isOpen())
    {
        return;
    }

    if (toPause)
    {
        setState(av::STATE_PAUSED);
        setPlayState(SL_PLAYSTATE_PAUSED);
    }
    else
    {
        setState(av::STATE_PLAYING);
        setPlayState(SL_PLAYSTATE_PLAYING);

        startEnqueFrame();
    }

}

void NativeAudioDevice::stop()
{
    if (!isOpen())
    {
        return;
    }

    setState(av::STATE_STOPPED);

    setPlayState(SL_PLAYSTATE_STOPPED);

    m_dataEvent.post();

    cleanup();

}


void NativeAudioDevice::flush()
{
    clearQueue();

    clearBufferQueue();
    clearUsedFrames();
}

bool NativeAudioDevice::isPaused()
{
    return (m_state != av::STATE_PLAYING);
}

int NativeAudioDevice::getState()
{
    return m_state;
}


void NativeAudioDevice::enable(bool enabled)
{
    {
        comn::AutoCritSec lock(m_cs);
        m_enabled = enabled;
    }

    if (!m_enabled)
    {
        flush();
    }
}

bool NativeAudioDevice::isEnabled()
{
    return m_enabled;
}


int64_t NativeAudioDevice::getCurTime(int64_t* pClock)
{
    if (pClock)
    {
        *pClock = m_timePoint.m_clock;
    }
    return m_timePoint.m_pts;
}


void NativeAudioDevice::writeFrame(MediaPacketPtr& frame)
{
    if (!isOpen() || (m_state == av::STATE_STOPPED))
    {
        return ;
    }

    if (!m_enabled)
    {
        assign(m_timePoint, frame);
        return ;
    }

    //CLog::debug("writeFrame. audio. length:%d\n", frame->size);

    m_frameCount ++;
    size_t frameSize = m_frameQueue.push(frame);
    m_dataEvent.post();

}



void NativeAudioDevice::audioCallback(SLAndroidSimpleBufferQueueItf bq)
{
    if (!m_enabled || (m_state != av::STATE_PLAYING))
    {
        return;
    }

    //CLog::info("audioCallback\n");

    MediaPacketPtr frame;
    while (m_state == av::STATE_PLAYING)
    {
        if (m_frameQueue.pop(frame))
        {
            break;
        }
        else
        {
            m_dataEvent.timedwait(-1);
        }
    }

    enqueFrame(frame, bq);
}

void NativeAudioDevice::setState(int state)
{
    comn::AutoCritSec lock(m_cs);
    m_state = state;
}

void NativeAudioDevice::clearQueue()
{
    m_frameQueue.clear();
}

void NativeAudioDevice::cleanup()
{
    clearQueue();
    clearUsedFrames();

    comn::AutoCritSec lock(m_cs);
    m_timePoint.reset();
    m_frameCount = 0;
}

void NativeAudioDevice::onNotEnoughData(int length)
{
    if ((m_frameCount == 0) || m_endOfStream)
    {
        return;
    }

    CLog::warning("not enough audio data. length:%d, queue:%d\n",
        length, m_frameQueue.size());
}



bool NativeAudioDevice::openDevice(int sampleRate, int channels)
{
    closeDevice();

    CLog::warning("NativeAudioDevice::openDevice. rate:%d, chl:%d\n", sampleRate, channels);

    // configure Audio PCM format
    SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                MAX_BUFFER_COUNT };

    m_pcmFormat.formatType = SL_DATAFORMAT_PCM;
    m_pcmFormat.numChannels = channels;
    m_pcmFormat.samplesPerSec  = sampleRate * 1000;
    m_pcmFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
    m_pcmFormat.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
    m_pcmFormat.endianness = SL_BYTEORDER_LITTLEENDIAN;
    if (m_pcmFormat.numChannels == 2)
    {
        m_pcmFormat.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
    }
    else
    {
        m_pcmFormat.channelMask = SL_SPEAKER_FRONT_CENTER;
    }

    // configure audio sink
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, m_outputMixObject};

    const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME,
                SL_IID_ANDROIDCONFIGURATION};
    const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    SLDataSource audioSrc = {&loc_bq, &m_pcmFormat};
    SLDataSink audioSnk = {&loc_outmix, NULL};

    // create audio player
    SLresult result = (*m_engineEngine)->CreateAudioPlayer(m_engineEngine, &m_playerObject,
            &audioSrc, &audioSnk, 3, ids, req);
    if(result != SL_RESULT_SUCCESS){
        CLog::warning("Can't create player: %d\n", result);
        return false;
    }

    // get config
    result = (*m_playerObject)->GetInterface(m_playerObject, SL_IID_ANDROIDCONFIGURATION, &m_playerConfig);
    if (m_playerConfig)
    {
        SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
        result = (*m_playerConfig)->SetConfiguration(m_playerConfig,
                SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32));
    }

    // realize the player
    result = (*m_playerObject)->Realize(m_playerObject, SL_BOOLEAN_FALSE);
    if(result != SL_RESULT_SUCCESS){
        CLog::warning("Can't realize player : %d\n", result);
        return false;
    }

    // get the play interface
    result = (*m_playerObject)->GetInterface(m_playerObject, SL_IID_PLAY, &m_playerPlay);
    if(result != SL_RESULT_SUCCESS){
        CLog::warning("Can't get play iface\n");
        return false;
    }

    // get volume
    result = (*m_playerObject)->GetInterface(m_playerObject, SL_IID_VOLUME, &m_playerVolume);

    // get the buffer queue interface
    result = (*m_playerObject)->GetInterface(m_playerObject, SL_IID_BUFFERQUEUE,
        &m_playerBufferQueue);
    if(result != SL_RESULT_SUCCESS){
        CLog::warning("Can't get buffer queue iface\n");
        return false;
    }

    // register callback on the buffer queue
    result = (*m_playerBufferQueue)->RegisterCallback(m_playerBufferQueue, slBufferQueueCallback, (void*)this);
    assert(SL_RESULT_SUCCESS == result);

    return true;
}

void NativeAudioDevice::closeDevice()
{
    if (m_playerObject)
    {
        (*m_playerObject)->Destroy(m_playerObject);
        m_playerObject = NULL;
        m_playerPlay = NULL;
        m_playerBufferQueue = NULL;
        m_playerVolume = NULL;
    }
}

bool NativeAudioDevice::isDeviceOpen()
{
    return (m_playerObject != NULL);
}

void NativeAudioDevice::assign(TimePoint& timePoint, MediaPacketPtr& frame)
{
    timePoint.reset(frame->pts, comn::ClockTime::getTime());
}

void NativeAudioDevice::saveTime(MediaPacketPtr& frame)
{
    assign(m_timePoint, frame);
}

bool NativeAudioDevice::openEngine()
{
    if (m_engineObject)
    {
        return true;
    }

    CLog::debug("openEngine\n");

    SLresult result;

    /* Create OpenSL ES engine in thread-safe mode */
    SLEngineOption EngineOption[] = {(SLuint32) SL_ENGINEOPTION_THREADSAFE, (SLuint32) SL_BOOLEAN_TRUE};

    //For future use
    const SLInterfaceID ids[2] = {SL_IID_AUDIODECODERCAPABILITIES, SL_IID_AUDIOENCODERCAPABILITIES};
    const SLboolean req[2] = { SL_BOOLEAN_FALSE, SL_BOOLEAN_FALSE};

    // create engine
    result = slCreateEngine(&m_engineObject, 1, EngineOption, 0, NULL, NULL);
    if(result != SL_RESULT_SUCCESS){
        CLog::warning("Can't create engine %d\n", result);
        return false;
    }

    // realize the engine
    result = (*m_engineObject)->Realize(m_engineObject, SL_BOOLEAN_FALSE);
    if(result != SL_RESULT_SUCCESS){
        CLog::warning("Can't realize engine\n");
        return false;
    }

    // get the engine interface, which is needed in order to create other objects
    result = (*m_engineObject)->GetInterface(m_engineObject, SL_IID_ENGINE, &m_engineEngine);
    if(result != SL_RESULT_SUCCESS){
        CLog::warning("Can't get engine iface\n");
        // Destroy engine since created
        (*m_engineObject)->Destroy(m_engineObject);
        return false;
    }

    result = (*m_engineEngine)->CreateOutputMix(m_engineEngine, &m_outputMixObject, 0, NULL, NULL);
    if(result != SL_RESULT_SUCCESS){
        CLog::warning("Can't create output mix\n");
        // Destroy engine since created
        (*m_engineObject)->Destroy(m_engineObject);
        return false;
    }

    result = (*m_outputMixObject)->Realize(m_outputMixObject, SL_BOOLEAN_FALSE);
    if(result != SL_RESULT_SUCCESS){
        CLog::warning("Can't realize output mix\n");
        // Destroy engine since created
        (*m_engineObject)->Destroy(m_engineObject);
        return false;
    }

    CLog::info("Opensl sound library initialized\n");
    return true;
}

void NativeAudioDevice::closeEngine()
{
    if (!m_engineObject)
    {
        return;
    }

    CLog::debug("closeEngine\n");

    // destroy output mix object, and invalidate all associated interfaces
    if (m_outputMixObject != NULL) {
        (*m_outputMixObject)->Destroy(m_outputMixObject);
        m_outputMixObject = NULL;
    }

    // destroy engine object, and invalidate all associated interfaces
    if (m_engineObject != NULL) {
        (*m_engineObject)->Destroy(m_engineObject);
        m_engineObject = NULL;
        m_engineEngine = NULL;
    }

}

void NativeAudioDevice::setPlayState(int state)
{
    if (!m_playerBufferQueue || !m_playerPlay)
    {
        return;
    }

    CLog::debug("NativeAudioDevice::setPlayState %d\n", state);

    // set the player's state to playing
    SLresult result = (*m_playerPlay)->SetPlayState(m_playerPlay, state);
}

void NativeAudioDevice::markAndSweep(MediaPacketPtr& pkt)
{
    size_t count = m_usedFrames.push(pkt);
    if (count > (MAX_BUFFER_COUNT * 2))
    {
        m_usedFrames.pop();
    }
}

void NativeAudioDevice::clearUsedFrames()
{
    m_usedFrames.clear();
}

void NativeAudioDevice::clearBufferQueue()
{
    if (!m_playerBufferQueue)
    {
        return;
    }

    (*m_playerBufferQueue)->Clear(m_playerBufferQueue);
}

void NativeAudioDevice::enqueFrame(MediaPacketPtr& frame, SLAndroidSimpleBufferQueueItf que)
{
    if (!frame)
    {
        return;
    }

    comn::AutoCritSec lock(m_cs);

    uint8_t* frameData = frame->data;
    int frameSize = frame->size;

    SLresult result = (*que)->Enqueue(que, frameData, frameSize);
    if (SL_RESULT_SUCCESS != result)
    {
        CLog::warning("We could not enqueue next player buffer !!! %d\n", result);
    }

    saveTime(frame);

    markAndSweep(frame);
}

void NativeAudioDevice::startEnqueFrame()
{
//    SLAndroidSimpleBufferQueueState state;
//    (*m_playerBufferQueue)->GetState(m_playerBufferQueue, &state);

    MediaPacketPtr frame;
    if (!m_frameQueue.pop(frame))
    {
        frame.reset(new MediaPacket());

        uint8_t buffer[128] = {0};
        frame->write(buffer, sizeof(buffer));
    }

    enqueFrame(frame, m_playerBufferQueue);
}



}
